package com.senmo.video.view;

import android.graphics.Bitmap;
import android.graphics.PointF;
import android.opengl.GLES20;
import android.opengl.GLException;
import android.opengl.GLSurfaceView;
import android.opengl.GLSurfaceView.Renderer;
import android.util.Log;

import java.nio.ByteBuffer;
import java.nio.IntBuffer;

import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;

public class GLFrameRenderer implements Renderer {

    private GLSurfaceView mTargetSurface;
    private GLProgram mGLProgram = new GLProgram();

    private int mVideoWidth;
    private int mVideoHeight;
    private int mSurfaceWidth;
    private int mSurfaceHeight;

    private ByteBuffer y;
    private ByteBuffer u;
    private ByteBuffer v;

    /**
     * 视频显示比例细数，初始值为1 ，最小为1
     */
    private float scale = 1;

    private int mPanX = 0 ;
    private int mPanY = 0;

    PointF lastDelta = new PointF(0, 0);

    public GLFrameRenderer(GLSurfaceView surface) {
        mTargetSurface = surface;
    }

    @Override
    public void onSurfaceCreated(GL10 gl, EGLConfig config) {
        if (!mGLProgram.isProgramBuilt()) {
            mGLProgram.buildProgram();
        }
    }

    @Override
    public void onSurfaceChanged(GL10 gl, int width, int height) {
        GLES20.glViewport(0, 0, width, height);
        this.mSurfaceWidth = width;
        this.mSurfaceHeight = height;
    }
    @Override
    public void onDrawFrame(GL10 gl) {
        synchronized (this) {
            if (y != null) {
                y.position(0);
                u.position(0);
                v.position(0);
                mGLProgram.buildTextures(y, u, v, mVideoWidth, mVideoHeight);
                GLES20.glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
                GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);

                setOpenGLOption();
                mGLProgram.drawFrame();
            }
        }

        /**
         * 如果截图的话
         */
//        if(shotScreen){
//            Bitmap bitmap = null;
//            shotScreen = false;
//            try{
//
//                bitmap= createBitmapFromGLSurface(0, 0, mTargetSurface.getWidth(),
//                        mTargetSurface.getHeight(), gl);
//            }catch (OutOfMemoryError e){
//            }
//            if(listener != null){
//                listener.shotScreenResult(bitmap);
//            }
//        }

        checkShotScreen(0, 0, gl);
    }

    private void setOpenGLOption(){
        int max_dim = GLES20.GL_MAX_VIEWPORT_DIMS;
        int startX = 0;
        int startY = 0;
        float sc = (float) mSurfaceHeight / (float) mSurfaceWidth;

        int videoH;
        int videoW = (int) (mSurfaceWidth * scale);
        videoW = videoW > max_dim ? max_dim : videoW;
        videoH = (int) (sc * (float) videoW);

        if (scale > 1) {
            startX = - ((videoW - mSurfaceWidth) / 2);
            startY = - ((videoH - mSurfaceHeight) / 2);
        } else if ( scale > 0 && scale < 1) {
            startX = (int) (mSurfaceWidth * scale / 2);
            startY = (int) (mSurfaceHeight * scale / 2);
        }

        int tempStartX = startX;
        int tempStartY = startY;
        int tempX = startX + (mPanX + (int) lastDelta.x);
        int tempY = startY + (mPanY + (int) lastDelta.y);
        startX = (Math.abs(tempX) + mSurfaceWidth) <= videoW ? tempX : (mSurfaceWidth - videoW);
        startX = startX > 0 ? 0 : startX;
        startY = (Math.abs(tempY) + mSurfaceHeight) <= videoH ? tempY : (mSurfaceHeight - videoH);
        startY = startY > 0 ? 0 : startY;
        lastDelta.set(startX - tempStartX, startY - tempStartY);

        GLES20.glViewport(startX, startY, videoW, videoH);
    }

    /**
     * 设置视频显示比例
     * @param scale 最小设置0, 则视频不显示
     */
    public void setZoom(float scale){
        if (scale > 0) {
            this.scale = scale;
        }
    }

    /**
     * 设置图片移动距离
     */
    public void pan(int x, int y){
        this.mPanX = x;
        this.mPanY = y;
    }

    private Bitmap createBitmapFromGLSurface(int x, int y, int w, int h, GL10 gl)
            throws OutOfMemoryError {
        int bitmapBuffer[] = new int[w * h];
        int bitmapSource[] = new int[w * h];
        IntBuffer intBuffer = IntBuffer.wrap(bitmapBuffer);
        intBuffer.position(0);

        try {
            gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
            int offset1, offset2;
            for (int i = 0; i < h; i++) {
                offset1 = i * w;
                offset2 = (h - i - 1) * w;
                for (int j = 0; j < w; j++) {
                    int texturePixel = bitmapBuffer[offset1 + j];
                    int blue = (texturePixel >> 16) & 0xff;
                    int red = (texturePixel << 16) & 0x00ff0000;
                    int pixel = (texturePixel & 0xff00ff00) | red | blue;
                    bitmapSource[offset2 + j] = pixel;
                }
            }
        } catch (GLException e) {
            return null;
        }

        return Bitmap.createBitmap(bitmapSource, w, h, Bitmap.Config.ARGB_8888);
    }

    private void checkShotScreen(int x, int y, GL10 gl)
            throws OutOfMemoryError {
        if (!shotScreen) {
            return;
        }
        shotScreen = false;

        final int w = mTargetSurface.getWidth();
        final int h = mTargetSurface.getHeight();
        final int[] bitmapBuffer;
        IntBuffer intBuffer;
        try {
            bitmapBuffer = new int[w * h]; // big buffer
            intBuffer = IntBuffer.wrap(bitmapBuffer);
            intBuffer.position(0);
        } catch (OutOfMemoryError err) {
            Log.e("GL", "get screen shot, allocate buffer oom", err);
            return;
        }

        try {
            gl.glReadPixels(x, y, w, h, GL10.GL_RGBA, GL10.GL_UNSIGNED_BYTE, intBuffer);
        } catch (GLException e) {
            return;
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                int i, j;
                int offset1, offset2;
                int pixel1, pixel2;
                int blue, red, texturePixel;
                int half = h / 2;

                for (i = 0; i < half; i++) {
                    offset1 = i * w;
                    offset2 = (h - i - 1) * w;
                    for (j = 0; j < w; j++) {
                        texturePixel = bitmapBuffer[offset1 + j];
                        blue = (texturePixel >> 16) & 0xff;
                        red = (texturePixel << 16) & 0x00ff0000;
                        pixel1 = (texturePixel & 0xff00ff00) | red | blue;

                        texturePixel = bitmapBuffer[offset2 + j];
                        blue = (texturePixel >> 16) & 0xff;
                        red = (texturePixel << 16) & 0x00ff0000;
                        pixel2 = (texturePixel & 0xff00ff00) | red | blue;

                        bitmapBuffer[offset1 + j] = pixel2;
                        bitmapBuffer[offset2 + j] = pixel1;
                    }
                }

                Bitmap bitmap;
                try {
                    bitmap = Bitmap.createBitmap(bitmapBuffer, w, h, Bitmap.Config.ARGB_8888);
                } catch (OutOfMemoryError err) {
                    Log.e("GL", "get screen shot, create bitmap oom", err);
                    return;
                }
                if (listener != null) {
                    listener.shotScreenResult(bitmap);
                }
            }
        }).start();
    }

    public void update(int w, int h,int yLen, int uLen , int vLen) {
        if (w > 0 && h > 0) {
            mGLProgram.createBuffers(GLProgram.squareVertices);

            if (w != mVideoWidth || h != mVideoHeight) {
                this.mVideoWidth = w;
                this.mVideoHeight = h;

                synchronized (this) {
                    y = ByteBuffer.allocate(yLen);
                    u = ByteBuffer.allocate(uLen);
                    v = ByteBuffer.allocate(vLen);
                }
            }
        }
    }

    public void update(byte[] yData, byte[] uData, byte[] vData) {
        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();
            y.put(yData, 0, yData.length);
            u.put(uData, 0, uData.length);
            v.put(vData, 0, vData.length);

        }
        // request to render
        mTargetSurface.requestRender();
    }

    public void update(byte[] data, int yLen, int uLen, int vLen) {
        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();
            y.put(data, 0, yLen);
            u.put(data, yLen, uLen);
            v.put(data, yLen + uLen, vLen);
        }
        // request to render
        mTargetSurface.requestRender();
    }

    public void updateBuffer(byte[] data,
                             int width, int height,
                             int yStride, int uvStride,
                             int yLen, int uLen, int vLen) {

        synchronized (this) {
            y.clear();
            u.clear();
            v.clear();

            if (width == yStride) {
                y.put(data, 0, yLen);
                u.put(data, yLen, uLen);
                v.put(data, yLen + uLen, vLen);
            } else {
                for (int i = 0; i < height; i ++){
                    y.put(data, i * yStride, width);
                    if(i < height / 2 ){
                        u.put(data, yLen + i * uvStride, (width / 2));
                        v.put(data, yLen + vLen + i * uvStride, (width / 2));
                    }
                }
            }
        }

        mTargetSurface.requestRender();
    }

    private boolean shotScreen = false;
    public void shotScreen(){
        shotScreen = true;
    }
    private SurfaceShotScreenListener listener;
    public void setSurfaceShotListener(SurfaceShotScreenListener listener){
        this.listener = listener;
    }
    public interface SurfaceShotScreenListener{
        void shotScreenResult(Bitmap bitmap);
    }
}
